﻿<!doctype html>
<head>
	<meta charset="utf-8" />
	<title>Threebox performance test</title>
	<script src="../dist/threebox.js" type="text/javascript"></script>
	<link href="../dist/threebox.css" rel="stylesheet" />
	<script src="config.js"></script>
	<link href="https://api.mapbox.com/mapbox-gl-js/v2.2.0/mapbox-gl.css" rel="stylesheet">
	<script src="https://api.mapbox.com/mapbox-gl-js/v2.2.0/mapbox-gl.js"></script>
	<script src="plugins/jquery.min.js"></script>
	<!-- Place your kit's code here -->
	<script src="css/fontawesome.js"></script>
	<link href="css/free.min.css" rel="stylesheet" />
	<link href="css/free-v4-font-face-min.css" rel="stylesheet" />
	<link href="css/free-v4-shims.min.css" rel="stylesheet" />

	<style>
		body, html {
			width: 100%;
			height: 100%;
			margin: 0;
		}

		#map {
			width: 100%;
			height: 100%;
		}

		#info {
			background-color: rgba(0,0,0,1);
		}

		.dg .folder .gui-stats {
			height: auto;
		}

		.top-right-tools {
			display: flex;
			right: 40px;
			z-index: 10;
		}

		.tools-i.mapboxgl-ctrl {
			margin: 10px 20px 0 0;
			display: inline-flex;
		}
	</style>
</head>
<body>

	<div id='map' class='map'>
		<div class="mapboxgl-ctrl-top-right top-right-tools" aria-label="Map Tools Control" style="margin-right: 220px">
			<div class="tools-i mapboxgl-ctrl mapboxgl-ctrl-group">
				<button type="button" name="dispose" id="dispose" title="Dispose all resources" alt="Dispose all resources"><i class="fas fa-broom"></i></button>
			</div>
		</div>
	</div>

	<script type="module">

		if (!config) console.error("Config not set! Make a copy of 'config_template.js', add in your access token, and save the file as 'config.js'.");

		mapboxgl.accessToken = config.accessToken;

		//starting location for both map and eventual sphere
		var origin = [-122.4340, 37.7353, 20];

		var map = new mapboxgl.Map({
			container: 'map',
			style: 'mapbox://styles/mapbox/light-v9',
			center: origin,
			zoom: 12,
			pitch: 60,
			antialias: true,
		});

		// we can add Threebox to mapbox to add built-in mouseover/mouseout and click behaviors
		window.tb = new Threebox(
			map,
			map.getCanvas().getContext('webgl'),
			{
				defaultLights: true,
				enableTooltips: true // change this to false to disable default tooltips on fill-extrusion and 3D models
			}
		);

		 
		import { GUI } from 'https://threejs.org/examples/jsm/libs/lil-gui.module.min.js';
		import Stats from 'https://threejs.org/examples/jsm/libs/stats.module.js';

		map.on('style.load', function () {

			// stats
			stats = new Stats();
			map.getContainer().appendChild(stats.dom);

			map.addLayer({
				id: 'custom_layer',
				type: 'custom',
				renderingMode: '3d',
				onAdd: function (map, mbxContext) {
					init();
					initMesh();
					animate();
				},

				render: function (gl, matrix) {
					tb.update();
				}
			})
		});

		let stats, gui, guiStatsEl;
		let geometry = false;
		let meshes = [];

		// gui
		var Method = {
			CLONED: 'CLONED',
			NOCLONED: 'NO CLONED'
		};

		var api = {
			method: Method.CLONED,
			count: 100,
			animation: true
		};

		// Creative Commons License attribution: Windmill animated model by https://sketchfab.com/data3anshow
		// from https://sketchfab.com/3d-models/windmill-animated-6ce5667e8d5c47068ea13196036efd52
		var model = { obj: 'models/windmill_a/windmill_a.gltf', type: 'gltf', scale: 0.1 };

		function initMesh() {

			let diff = api.count - tb.world.children.length;
			if (diff == 0) return;
			console.log(api.count);

			if (tb.world.children.length > api.count) {
				console.time("(clear)");
				for (let j = tb.world.children.length - 1; j >= api.count; j--) {
					var obj = tb.world.children[j];
					tb.remove(obj);
				}
				getGeometryTotalLength();

				map.repaint = true;
				console.timeEnd("(clear)");

			} else {
				var options = {
					obj: model.obj, 
					type: model.type,
					scale: model.scale,
					units: 'meters',
					rotation: { x: 90, y: 90, z: 0 },
					anchor: 'center',
					cloned: (api.method == Method.CLONED ? true : false)
				}
				if (!processing) makeNaive(options, diff);

			}

		}

		let processing = false;

		function makeNaive(options, diff) {
			processing = true;
			let j = 0;
			for (var i = 0; i < diff; i++) {

				tb.loadObj(options, function (model) {
					if (j == 0) console.time(api.method + ' (build)');
					j++;
					if (!geometry) {
						model.traverse(function (object) {
							if (object.isMesh) meshes.push(object.geometry);
						});
						geometry = true;
					}

					let lng = origin[0] + Math.random() * 0.4 - 0.2;
					let lat = origin[1] + Math.random() * 0.4 - 0.2;
					let alt = origin[2] + Math.random() * 0.4 - 0.2;
					let obj = model.setCoords([lng, lat, alt]);

					tb.add(obj);

					if (api.animation) {
						// play default animation, for 10 seconds
						let opt = { animation: 0, duration: 10000 };
						obj.playDefault(opt);
					}

					getGeometryTotalLength();

					if (j == diff) {
						console.timeEnd(api.method + ' (build)');
						console.log("Items: " + tb.world.children.length);
						processing = false;
					}
				})

			}
			map.repaint = true;
		}

		function init() {

			// gui

			gui = new GUI();
			gui.add(api, 'method', Method).onChange(initMesh);
			gui.add(api, 'count', 0, 10000).step(10).onChange(initMesh);
			gui.add(api, 'animation').name('animation');

			var perfFolder = gui.addFolder('Performance');

			guiStatsEl = document.createElement('li');
			guiStatsEl.classList.add('gui-stats');

			perfFolder.$children.appendChild(guiStatsEl);
			perfFolder.open();

		}

		function animate() {
			requestAnimationFrame(animate);
			stats.update();
		}

		function getGeometryTotalLength() {
			if (!meshes && meshes.length == 0) return;
			var geometryByteLength = 0;
			meshes.forEach(function (g) {
				geometryByteLength += getGeometryByteLength(g);
			});
			let multiplier = (api.method == Method.CLONED ? 1 : api.count);
			guiStatsEl.innerHTML = [

				'<i>GPU draw calls</i>: ' + multiplier,
				'<i>GPU memory</i>: ' + formatBytes(multiplier * 16 + geometryByteLength, 2),
				'<i>WebGL geometries</i>: ' + tb.memory().geometries * multiplier,
				'<i>WebGL textures</i>: ' + tb.memory().textures * multiplier,
				'<i>WebGL programs</i>: ' + tb.programs() * multiplier

			].join('<br/>');
		}

		function getGeometryByteLength(geometry) {

			var total = 0;

			if (geometry.index) total += geometry.index.array.byteLength;

			for (var name in geometry.attributes) {

				total += geometry.attributes[name].array.byteLength;

			}

			return total; 

		}

		// Source: https://stackoverflow.com/a/18650828/1314762
		function formatBytes(bytes, decimals) {

			if (bytes === 0) return '0 bytes';

			var k = 1024;
			var dm = decimals < 0 ? 0 : decimals;
			var sizes = ['bytes', 'KB', 'MB'];

			var i = Math.floor(Math.log(bytes) / Math.log(k));

			return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];

		}

		function createStatusKPI(text, complianceStatus) {
			let cssText = ['text-secondary', 'text-secondary', 'text-success', 'text-red', 'text-yellow'];
			let cssSymbol = ['', 'fa-circle', '', 'fa-exclamation-circle', 'fa-exclamation-triangle'];
			//let value = getRandomInt(1, 100);
			//let index = Math.floor(value / 34);
			let index = 0;

			if (complianceStatus === 'NOCOMPLIANT') {
				index = 4;
			}
			else if (complianceStatus === 'FAULT') {
				index = 3;
			}
			else if (complianceStatus === 'COMPLIANT') {
				return; //index = 2;
			}
			else if (complianceStatus === 'UNKNOWN') {
				return; //index = 1;
			}
			else if (complianceStatus === 'NOSTATUS') {
				return; //index = 0;
			}

			let popup = document.createElement('div');
			popup.innerHTML = '<i class="fas ' + cssSymbol[index] + ' ' + cssText[index] + '" title="' + text + '"></i>';

			return popup;
		}


		$('#dispose').on('click', function () {
			const result = window.tb.dispose();
			console.log(result);
			window.tb = {};
		});

	</script>

</body>
